/*  This program is free software; you can redistribute it and/or modify it
    under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation; either version 2.1 of the License, or (at
    your option) any later version.
    For more details, see the GNU Lesser General Public License (www.fsf.org
    or the COPYING file somewhere in the package)
 */

/*! @file OSGi/Signal.inl
    @brief Class OSGi::Signal inline methods.
    @author @ref Guillaume_Terrissol
    @date 8th January 2010 - 11th April 2014
    @note This file is distributed under the LGPL license.
    Refer to the file COPYING (or http://www.fsf.org) for more information.
 */

namespace OSGi
{
//------------------------------------------------------------------------------
//                                  Connection
//------------------------------------------------------------------------------

    /// @cond DEVELOPMENT

    /*! Defaulted.
     */
    template<class... TArgs>
    Signal<TArgs...>::Signal() = default;


    /*! Defaulted.
     */
    template<class... TArgs>
    Signal<TArgs...>::~Signal() = default;


    /*! @param pName   Slot name
        @param pTarget Slot target
        @param pSlot   The slot itself
        @tparam TRecv  Target type
     */
    template<class... TArgs>
    template<class TRecv>
    void Signal<TArgs...>::connect(const std::string& pName, std::shared_ptr<TRecv> pTarget, void (TRecv::*pSlot)(TArgs...))
    {
        mSlots[pName].push_back(std::make_shared<Slot<TRecv>>(pTarget, pSlot));
    }


    /*! @param pName   Slot name
        @param pTarget Slot target
     */
    template<class... TArgs>
    void Signal<TArgs...>::disconnect(const std::string& pName, std::shared_ptr<void> pTarget)
    {
        auto    lNamedSlots = mSlots.find(pName);
        if (lNamedSlots != end(mSlots))
        {
            Slots&  lSlots = lNamedSlots->second;
            for(auto lIter = begin(lSlots); lIter != end(lSlots);)
            {
                SlotBase*   lSlot = lIter->get();
                if (lSlot->expired() || (lSlot->target() == pTarget.get()))
                {
                    std::swap(*lIter, lSlots.back());
                    lSlots.pop_back();
                }
                else
                {
                    ++lIter;
                }
            }
        }
    }


    /*! @param pArgs Parameters to emit the signal with
     */
    template<class... TArgs>
    void Signal<TArgs...>::operator()(TArgs... pArgs) const
    {
        for(const auto& lNamedSlots : mSlots)
        {
            const Slots&    lSlots = lNamedSlots.second;
            for(auto lIter = begin(lSlots); lIter != end(lSlots);)
            {
                SlotBase*   lSlot = lIter->get();
                if (!lSlot->expired())
                {
                    (*lSlot)(std::forward<TArgs>(pArgs)...);
                    ++lIter;
                }
                else
                {
                    // const_cast because this is a small optimization, and I wanted to avoid the mutable keyword in such a case.
                    std::swap(const_cast<SlotBasePtr&>(*lIter), const_cast<Slots&>(lSlots).back());
                    const_cast<Slots&>(lSlots).pop_back();
                }
            }
        }
    }


    /*! Defaulted.
     */
    template<class... TArgs>
    Signal<TArgs...>::SlotBase::SlotBase() = default;


    /*! Defaulted.
     */
    template<class... TArgs>
    Signal<TArgs...>::SlotBase::~SlotBase() = default;


    /*! @param pArgs Parameters to call the slot with
     */
    template<class... TArgs>
    inline void Signal<TArgs...>::SlotBase::operator()(TArgs&&... pArgs) const
    {
        call(std::forward<TArgs>(pArgs)...);
    }


    /*! @return Whether or not the slot target has expired
     */
    template<class... TArgs>
    inline bool Signal<TArgs...>::SlotBase::expired() const
    {
        return hasExpired();
    }


    /*! @return A valid target, or @b nullptr if it has expired
     */
    template<class... TArgs>
    inline void* Signal<TArgs...>::SlotBase::target()
    {
        return getTarget();
    }


    /*! @param pTarget Object a member of which is the slot
        @param pFunc   Function to be called
     */
    template<class... TArgs>
    template<class TRecv>
    Signal<TArgs...>::Slot<TRecv>::Slot(std::shared_ptr<TRecv> pTarget, void (TRecv::*pFunc)(TArgs...))
        : mTarget(pTarget)
        , mFunc(pFunc)
    { }


    /*! Defaulted.
     */
    template<class... TArgs>
    template<class TRecv>
    Signal<TArgs...>::Slot<TRecv>::~Slot() = default;


    /*! @copydetails Signal<TArgs...>::SlotBase::operator()(TArgs&&... pArgs) const
     */
    template<class... TArgs>
    template<class TRecv>
    void Signal<TArgs...>::Slot<TRecv>::call(TArgs&&... pArgs) const
    {
        if (auto lTarget = mTarget.lock())
        {
            // Scary, isn't it ?
            ((*lTarget).*mFunc)(std::forward<TArgs>(pArgs)...);
        }
    }


    /*! Checks whether the slot target does still exist.
        @retval true  The target has expired
        @retval false The target still exists
     */
    template<class... TArgs>
    template<class TRecv>
    bool Signal<TArgs...>::Slot<TRecv>::hasExpired() const
    {
        return mTarget.expired();
    }


    /*! @copydetails Signal<TArgs...>::SlotBase::target()
     */
    template<class... TArgs>
    template<class TRecv>
    void* Signal<TArgs...>::Slot<TRecv>::getTarget()
    {
        if (mTarget.expired())
        {
            return nullptr;
        }
        else
        {
            return mTarget.lock().get();
        }
    }

    /// @endcond
}


//------------------------------------------------------------------------------
//                             Macros Documentation
//------------------------------------------------------------------------------

    /*! @def OSGI_DECL_SIGNAL(func,...)
        This macro allows defining a signal in a class declaration
        @param func Name of the signal, to fire with the @ref OSGI_EMIT macro
        @warning This macro [re]opens the public section of the class
     */

    /*! @def OSGI_SIGNAL(name)
        @param name Signal name, as defined in a class thanks to the OSGI_DECL_SIGNAL()
        macro
        Use this macro while connecting a signal to a slot.
        @sa OSGI_CONNECT, OSGI_DISCONNECT
     */

    /*! @def OSGI_SLOT(name)
        @param name Name of a method compatible with the signal it is bound to
        Use this macro while connecting a signal to a slot.
        @sa OSGI_CONNECT, OSGI_DISCONNECT
     */

    /*! @def OSGI_CONNECT(sender, signal, receiver, slot)
        Connects the signal of an objet to the slot of a receiver.
        @param sender   Shared pointer to the signal sender
        @param signal   Name of the signal to connect
        @param receiver Shared pointer to the signal receiver
        @param slot     Name of the slot (bound method)
     */

    /*! @def OSGI_DISCONNECT(sender, signal, receiver, slot)
        Disconnects a signal previously bound to a slot.
        @param sender   Shared pointer to the signal sender
        @param signal   Name of the signal to connect
        @param receiver Shared pointer to the signal receiver
        @param slot     Name of the slot (bound method)
     */

    /*! @def OSGI_EMIT
        Use this macro to fire a signal.@n
        Even if this macro expand to nothing, it is usefull to easily check every
        fired signal in a program.
     */
